#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JD Redis监听器
监听Redis任务队列，自动执行JD爬取
"""

import redis
import json
import time
import requests
import logging
import pickle
import os
from faker import Faker
from selenium.webdriver.common.by import By

# 处理相对导入问题
try:
    from .scraper_clean import JDScraper
except ImportError:
    # 如果相对导入失败，尝试绝对导入
    import sys
    import os
    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from JD.scraper_clean import JDScraper

# 配置日志 - 修复编码问题
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('jd_monitor.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


def get_random_ua():
    """获取随机User-Agent"""
    fake = Faker()
    return fake.user_agent()


# Redis配置
REDIS_CONFIG = {
    'host': '8.155.9.53',
    'port': 6300,
    'password': 'jhkdjhkjdhsIUTYURTU_XF6dAk',
    'task_key': ':1:自动获取产品列表'
}

# API配置
API_CONFIG = {
    'url': 'http://192.168.5.12:8000/api/v1/products/batch_add_processed_products/',
    'headers': {'Content-Type': 'application/json', 'User-Agent': get_random_ua()}
}

class JDRedisMonitor:
    """JD Redis任务监听器"""
    
    def __init__(self, redis_config=None, api_config=None):
        self.redis_config = redis_config or REDIS_CONFIG
        self.api_config = api_config or API_CONFIG
        
        # Redis连接
        self.redis_client = redis.Redis(
            host=self.redis_config['host'],
            port=self.redis_config['port'],
            password=self.redis_config['password'],
            decode_responses=False  # 获取原始字节数据
        )
        
        logger.info(f"JD Redis监听器已初始化")
        logger.info(f"Redis: {self.redis_config['host']}:{self.redis_config['port']}")
        logger.info(f"API: {self.api_config['url']}")
    
    def get_redis_tasks(self):
        """获取Redis中的任务列表 """
        try:
            raw_data = self.redis_client.get(self.redis_config['task_key'])
            
            if not raw_data:
                return []
            
            # 智能解析数据（兼容pickle和JSON）
            try:
                data = pickle.loads(raw_data)
                if isinstance(data, str):
                    return json.loads(data)
                elif isinstance(data, list):
                    return data
                else:
                    return []
            except:
                try:
                    text_data = raw_data.decode('utf-8')
                    return json.loads(text_data)
                except:
                    logger.error("❌ 无法解析Redis数据格式")
                    return []
                    
        except Exception as e:
            logger.error(f"❌ 获取Redis任务失败: {e}")
            return []
    
    def update_redis_tasks(self, task_list):
        """更新Redis中的任务列表"""
        try:
            if task_list:
                json_data = json.dumps(task_list, ensure_ascii=False)
                self.redis_client.set(self.redis_config['task_key'], json_data.encode('utf-8'))
                logger.info(f"💾 已更新Redis，剩余任务: {len(task_list)}")
            else:
                self.redis_client.delete(self.redis_config['task_key'])
                logger.info("🗑️ 任务队列已清空")
        except Exception as e:
            logger.error(f"❌ 更新Redis失败: {e}")
    
    def _validate_task_data(self, task):
        """验证任务数据的完整性和有效性"""
        logger.info("🔍 开始验证任务数据...")
        
        # 1. 检查必要字段是否存在
        required_fields = ["store_id", "store_url", "platform"]
        missing_fields = []
        
        for field in required_fields:
            if not task.get(field):
                missing_fields.append(field)
        
        if missing_fields:
            logger.error(f"❌ 缺少必要字段: {', '.join(missing_fields)}")
            logger.error(f"任务内容: {task}")
            return False
        
        # 2. 验证平台信息
        platform = task.get("platform", "").strip()
        if platform != "京东":
            logger.error(f"❌ 平台不匹配，期望: '京东'，实际: '{platform}'")
            logger.error(f"任务内容: {task}")
            return False
        
        # 3. 验证store_id格式
        store_id = task.get("store_id", "").strip()
        if not store_id or len(store_id) < 5:
            logger.error(f"❌ store_id格式无效: '{store_id}'")
            logger.error(f"任务内容: {task}")
            return False
        
        # 4. 验证store_url格式
        store_url = task.get("store_url", "").strip()
        if not self._validate_jd_url(store_url):
            logger.error(f"❌ store_url格式无效: '{store_url}'")
            logger.error(f"任务内容: {task}")
            return False
        
        logger.info("✅ 任务数据验证通过")
        logger.info(f"   平台: {platform}")
        logger.info(f"   店铺ID: {store_id}")
        logger.info(f"   店铺URL: {store_url}")
        return True
    
    def _validate_jd_url(self, url):
        """验证JD URL的有效性"""
        if not url:
            return False
        
        # 检查基本URL格式
        if not (url.startswith("http://") or url.startswith("https://")):
            return False
        
        # 检查是否为JD域名
        valid_domains = [
            "mall.jd.com",
            "item.jd.com", 
            "shop.jd.com",
            "www.jd.com"
        ]
        
        is_valid_domain = any(domain in url for domain in valid_domains)
        if not is_valid_domain:
            return False
        
        # 对于商城店铺URL，检查特定格式
        if "mall.jd.com" in url:
            # 期望格式: https://mall.jd.com/view_search-店铺ID-其他参数.html
            if "view_search-" not in url:
                logger.warning(f"⚠️  JD店铺URL格式可能不标准: {url}")
                # 不直接返回False，允许继续尝试
        
        return True

    def get_jd_login_mode(self):
        """获取JD登录方式"""
        print("\n🔐 请选择JD登录方式:")
        print("  1. 交互式登录（终端输入用户名密码，程序自动填写）")
        print("  2. 手动登录（浏览器中完全手动操作，适合有验证码的情况）")
        
        while True:
            choice = input("请选择 (1/2, 默认1): ").strip()
            if choice in ['', '1']:
                return "interactive"
            elif choice == '2':
                return "manual"
            else:
                print("❌ 无效选择，请输入 1 或 2")

    def manual_jd_login_process(self, scraper):
        """JD手动登录流程"""
        try:
            logger.info("🌐 正在打开JD登录页面...")
            # 导航到JD登录页面
            scraper.driver.get("https://passport.jd.com/new/login.aspx")
            
            print("\n" + "="*60)
            print("🔐 JD手动登录模式")
            print("="*60)
            print("📋 请在浏览器中完成以下操作：")
            print("   1. 选择登录方式（账号密码/短信/扫码等）")
            print("   2. 输入用户名和密码")
            print("   3. 完成验证码验证（如果有）")
            print("   4. 点击登录按钮")
            print("   5. 等待登录成功")
            print("   6. 登录成功后回到此窗口按 Enter 键继续")
            print("-" * 60)
            
            # 等待用户手动登录
            input("✋ 请在浏览器中完成登录，然后按 Enter 键继续...")
            
            # 验证登录状态
            logger.info("🔍 验证JD登录状态...")
            
            # 检查当前URL是否表示登录成功
            current_url = scraper.driver.current_url
            if "login" not in current_url.lower() and ("jd.com" in current_url or "360buy.com" in current_url):
                logger.info("✅ JD登录验证成功")
                return True
            else:
                # 尝试其他方式验证登录状态
                try:
                    import time
                    time.sleep(2)
                    
                    # 检查是否有用户信息元素
                    try:
                        # 检查是否能找到登录后的用户信息
                        user_info = scraper.driver.find_element(By.CSS_SELECTOR, ".nickname, .user-name, #ttbar-login .nickname")
                        if user_info:
                            logger.info("✅ 检测到用户信息，JD登录成功")
                            return True
                    except:
                        pass
                    
                    # 如果还在登录页面，说明登录失败
                    if "login" in scraper.driver.current_url.lower():
                        logger.warning("⚠️ 仍在登录页面，可能登录失败")
                        retry = input("是否重试登录？(y/n): ").strip().lower()
                        if retry in ['y', 'yes']:
                            return self.manual_jd_login_process(scraper)
                        return False
                    else:
                        logger.info("✅ 已离开登录页面，假定JD登录成功")
                        return True
                        
                except Exception as e:
                    logger.warning(f"JD登录状态验证出错: {e}")
                    # 如果验证出错，询问用户
                    success = input("JD登录是否成功？(y/n): ").strip().lower()
                    return success in ['y', 'yes']
                    
        except Exception as e:
            logger.error(f"❌ JD手动登录过程出错: {e}")
            return False

    def process_jd_task(self, task):
        """处理单个JD任务"""
        # 1. 严格验证任务数据完整性
        if not self._validate_task_data(task):
            logger.error("❌ 任务数据验证失败，终止处理")
            return True  # 返回True删除无效任务
        
        store_id = task.get("store_id")
        store_url = task.get("store_url")
        platform = task.get("platform", "").strip()
        
        # 获取重试次数
        retry_count = task.get("retry_count", 0)
        max_retries = 3  # 最大重试次数
        
        logger.info(f"🔍 处理JD任务: {store_url} (store_id: {store_id}, 重试次数: {retry_count}/{max_retries})")
        
        # 如果重试次数超过限制，标记为永久失败
        if retry_count >= max_retries:
            logger.error(f"❌ 任务重试次数超过限制 ({max_retries})，标记为永久失败")
            return True  # 返回True以删除任务，避免无限重试
        
        # 获取JD登录方式
        login_mode = self.get_jd_login_mode()
        if login_mode is None:
            logger.error("❌ 未选择登录方式")
            return False
        
        products = []
        success = False
        api_products = []
        
        try:
            # 使用JD爬虫抓取数据 - 改为可见模式便于调试
            logger.info("🚀 启动JD爬虫...")
            with JDScraper(headless=False, debug=True) as scraper:
                login_success = False
                
                if login_mode == "interactive":
                    logger.info("🤖 开始交互式登录JD...")
                    login_success = scraper.login_jd()
                elif login_mode == "manual":
                    logger.info("👤 开始手动登录模式...")
                    login_success = self.manual_jd_login_process(scraper)
                
                if login_success:
                    logger.info("JD登录成功")
                    
                    logger.info("正在导航到店铺页面...")
                    if scraper.navigate_to_shop(store_url):
                        logger.info("成功导航到店铺页面")
                        
                        # 等待页面完全加载
                        import time
                        time.sleep(3)
                        
                        logger.info("正在获取商品列表...")
                        # 获取商品列表
                        product_list = scraper.get_product_list(max_pages=None)  # 获取所有页
                        
                        if product_list:
                            logger.info(f"获取到 {len(product_list)} 个商品，开始提取详情...")
                            
                            # 获取商品详情
                            detailed_products = scraper.get_product_details(product_list)
                            
                            if detailed_products:
                                logger.info(f"成功获取 {len(detailed_products)} 个商品详情")
                                
                                # 转换为API格式
                                logger.info("正在转换为API格式...")
                                for product in detailed_products:
                                    api_data = scraper.convert_to_api_format(product)
                                    if api_data:
                                        api_products.append(api_data)
                                
                                logger.info(f"已转换为API格式，商品数量: {len(api_products)}")
                                success = True
                            else:
                                logger.warning("未获取到商品详情")
                        else:
                            logger.warning("未获取到商品列表")
                            # 添加调试信息
                            try:
                                logger.info(f"调试信息 - 当前URL: {scraper.driver.current_url}")
                                logger.info(f"调试信息 - 页面标题: {scraper.driver.title}")
                                
                                # 检查页面是否有商品元素
                                from selenium.webdriver.common.by import By
                                product_elements = scraper.driver.find_elements(By.CSS_SELECTOR, ".gl-item, .jSubObject")
                                logger.info(f"找到商品元素数量: {len(product_elements)}")
                                
                            except Exception as debug_e:
                                logger.error(f"调试检查失败: {debug_e}")
                    else:
                        logger.error("无法导航到店铺页面")
                else:
                    logger.error("JD登录失败")
                    
        except Exception as e:
            logger.error(f"❌ JD爬取异常: {e}")
            import traceback
            logger.error(traceback.format_exc())
        finally:
            logger.info("🔄 JD爬虫任务处理完成，准备继续监听...")
        
        # 提交到API
        try:
            payload = {
                "store_id": store_id,
                "products": api_products,
                "status": success
            }
            
            logger.info(f"🚀 提交数据到API: store_id={store_id}, 商品数量={len(api_products)}, status={success}")
            
            response = requests.post(
                self.api_config['url'],
                json=payload,
                headers=self.api_config['headers'],
                timeout=60
            )
            
            if response.status_code == 200:
                logger.info(f"✅ API提交成功: {store_id}")
                return True
            else:
                logger.error(f"❌ API提交失败: {response.status_code} - {response.text}")
                return False
                
        except Exception as e:
            logger.error(f"❌ API请求异常: {e}")
            return False
    
    def start_monitoring(self, poll_interval=5):
        """开始监听Redis任务队列"""
        logger.info(f"🎧 开始监听任务队列，轮询间隔: {poll_interval}秒")
        logger.info("💡 使用 Ctrl+C 停止监听")
        
        while True:
            try:
                logger.debug("🔍 检查Redis任务队列...")
                # 获取任务列表
                task_list = self.get_redis_tasks()
                
                if not task_list:
                    logger.info("😴 当前无任务，等待新任务...")
                    time.sleep(poll_interval)
                    continue
                
                logger.info(f"📝 发现 {len(task_list)} 个任务")
                
                # 找到第一个JD任务并进行初步验证
                jd_task = None
                jd_task_index = -1
                invalid_tasks = []
                
                for i, task in enumerate(task_list):
                    platform = task.get("platform", "").strip()
                    
                    # 记录所有平台的任务用于调试
                    if platform:
                        logger.debug(f"任务 {i+1}: 平台={platform}, store_id={task.get('store_id', 'None')}")
                    
                    if platform == "京东":
                        # 找到JD任务，但先不验证详细数据（在process_jd_task中验证）
                        jd_task = task
                        jd_task_index = i
                        break
                    elif platform and platform != "京东":
                        # 记录非JD任务，便于调试
                        logger.debug(f"跳过非JD任务: 平台={platform}")
                    else:
                        # 记录可能有问题的任务
                        invalid_tasks.append((i, task))
                
                # 如果有明显无效的任务，记录警告
                if invalid_tasks:
                    logger.warning(f"发现 {len(invalid_tasks)} 个可能无效的任务（缺少platform字段）")
                    for idx, task in invalid_tasks:
                        logger.warning(f"  任务 {idx+1}: {task}")
                
                if jd_task is None:
                    logger.info("📭 暂无JD任务，继续等待...")
                    time.sleep(poll_interval)
                    continue
                
                logger.info(f"📤 开始处理JD任务: {jd_task}")
                
                # 处理任务（先不删除）
                success = self.process_jd_task(jd_task)
                
                if success:
                    # 只有成功时才从Redis删除任务
                    task_list.pop(jd_task_index)
                    self.update_redis_tasks(task_list)
                    logger.info("✅ JD任务执行成功，已从队列删除")
                    logger.info("🔄 继续监听新任务...")
                else:
                    # 失败时更新重试次数
                    retry_count = jd_task.get("retry_count", 0) + 1
                    task_list[jd_task_index]["retry_count"] = retry_count
                    
                    max_retries = 3
                    if retry_count >= max_retries:
                        # 超过重试次数，删除任务
                        task_list.pop(jd_task_index)
                        logger.error(f"❌ 任务重试次数已达上限 ({max_retries})，从队列中删除")
                    else:
                        # 未超过重试次数，保留任务并更新重试计数
                        logger.error(f"❌ JD任务执行失败，重试次数: {retry_count}/{max_retries}")
                        logger.info("⏰ 任务保留在队列中，等待下次重试...")
                    
                    # 更新Redis
                    self.update_redis_tasks(task_list)
                    time.sleep(poll_interval * 2)  # 失败后等待更长时间再重试
                
                # 短暂休息后继续
                logger.debug("⏱️ 等待1秒后继续监听...")
                time.sleep(1)
                
            except KeyboardInterrupt:
                logger.info("🛑 收到中断信号，停止监听")
                break
            except Exception as e:
                logger.error(f"❌ 监听循环异常: {e}")
                import traceback
                logger.error(traceback.format_exc())
                logger.info("⏰ 等待后重新开始监听...")
                time.sleep(poll_interval)
    
    def test_connection(self):
        """测试连接"""
        try:
            # 测试Redis连接
            self.redis_client.ping()
            logger.info("✅ Redis连接正常")
            
            # JD使用交互式登录
            logger.info("💡 JD将在处理任务时进行交互式登录")
            
            return True
            
        except Exception as e:
            logger.error(f"❌ 连接测试失败: {e}")
            return False


def main():
    """主函数 - 提供菜单选择功能"""
    print("🚀 JD Redis监听器")
    print("=" * 40)
    
    monitor = JDRedisMonitor()
    
    # 测试连接
    print("🔍 测试连接...")
    if not monitor.test_connection():
        print("❌ 连接测试失败，请检查配置")
        return
    
    print("✅ 连接测试成功")
    
    while True:
        print("\n" + "="*40)
        print("📋 JD Redis 任务管理器")
        print("="*40)
        print("1. 查看当前任务")
        print("2. 添加JD测试任务")
        print("3. 添加自定义JD任务")
        print("4. 清空所有任务")
        print("5. 🎧 启动监听器")
        print("6. 退出")
        print("-"*40)
        
        choice = input("请选择操作 (1-6): ").strip()
        
        if choice == "1":
            # 查看当前任务
            tasks = monitor.get_redis_tasks()
            if tasks:
                print(f"\n📋 当前任务数量: {len(tasks)}")
                for i, task in enumerate(tasks, 1):
                    platform = task.get('platform', '未知')
                    store_id = task.get('store_id', '未知')
                    store_url = task.get('store_url', '未知')
                    retry_count = task.get('retry_count', 0)
                    
                    # 验证任务数据
                    is_valid = monitor._validate_task_data(task) if platform == "京东" else "跳过验证"
                    validity_status = "✅" if is_valid is True else "❌" if is_valid is False else "⏭️"
                    
                    print(f"  {i}. 平台: {platform}, Store ID: {store_id}, 重试: {retry_count} {validity_status}")
                    print(f"     URL: {store_url}")
                    
                    if platform == "京东" and is_valid is False:
                        print(f"     ⚠️  任务数据验证失败，此任务可能无法正常执行")
            else:
                print("\n📭 当前无任务")
        
        elif choice == "2":
            # 添加测试任务 - 允许用户输入URL或使用默认URL
            print("\n📝 添加JD测试任务:")
            print("默认测试URL: https://mall.jd.com/view_search-2253208-0-99-1-24-1.html")
            store_url = input("输入店铺URL (回车使用默认): ").strip()
            
            if not store_url:
                store_url = "https://mall.jd.com/view_search-2253208-0-99-1-24-1.html"
                print("✅ 使用默认测试URL")
            
            if "jd.com" in store_url:
                import uuid
                test_task = {
                    "store_id": str(uuid.uuid4()),
                    "store_url": store_url,
                    "platform": "京东"
                }
                
                tasks = monitor.get_redis_tasks()
                tasks.append(test_task)
                monitor.update_redis_tasks(tasks)
                print(f"✅ 已添加JD测试任务: {test_task['store_id']}")
                print(f"📍 URL: {store_url}")
            else:
                print("❌ 请输入有效的JD店铺URL")
        
        elif choice == "3":
            # 添加自定义任务
            print("\n📝 输入JD任务信息:")
            store_url = input("JD店铺URL: ").strip()
            
            if store_url and "jd.com" in store_url:
                import uuid
                custom_task = {
                    "store_id": str(uuid.uuid4()),
                    "store_url": store_url,
                    "platform": "京东"
                }
                
                tasks = monitor.get_redis_tasks()
                tasks.append(custom_task)
                monitor.update_redis_tasks(tasks)
                print(f"✅ 已添加自定义JD任务: {custom_task['store_id']}")
            else:
                print("❌ 请输入有效的JD店铺URL")
        
        elif choice == "4":
            # 清空任务
            confirm = input("\n⚠️  确认清空所有任务？(y/n): ").strip().lower()
            if confirm in ['y', 'yes', '是']:
                monitor.update_redis_tasks([])
                print("🗑️ 已清空所有任务")
        
        elif choice == "5":
            # 启动监听器
            print("\n🎧 启动JD Redis监听器...")
            print("💡 使用 Ctrl+C 停止监听")
            try:
                monitor.start_monitoring(poll_interval=5)
            except KeyboardInterrupt:
                print("\n🛑 监听器已停止")
        
        elif choice == "6":
            print("\n👋 退出程序")
            break
        
        else:
            print("❌ 无效选择，请重新输入")


if __name__ == "__main__":
    main() 